using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

using DeflateLib;

namespace CopyDeflate
{
    /// <summary>
    /// Example command line utils how to use DeflateLib.
    /// 
    /// Not fully tested.
    /// </summary>
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 4)
            {
                PrintUsage(); return;
            }

            string inputFormat = args[0].ToLowerInvariant();
            string inputFile = args[1];
            string outputFormat = args[2].ToLowerInvariant();
            string outputFile = args[3];

            try
            {
                if (!IsValidFormat(inputFormat)) throw new ApplicationException("Invalid input format");
                if (!IsValidFormat(outputFormat)) throw new ApplicationException("Invalid output format");

                WriteData(outputFormat, outputFile,
                    ReadData(inputFormat, inputFile));
            }
            catch(Exception ex)
            {
                Console.Error.WriteLine(ex);
            }
        }

        static bool IsValidFormat(string format)
        {
            return format == BinaryFormat || format == DeflateFormat ||
                format == ZLibFormat || format == GZipFormat;
        }

        const string BinaryFormat = "-binary";
        const string DeflateFormat = "-deflate";
        const string ZLibFormat = "-zlib";
        const string GZipFormat = "-gzip";

        static void WriteData(string format, string path, IEnumerable<ArraySegment<byte>> data)
        {
            if (format == BinaryFormat)
            {
                using (FileStream fs = File.Create(path))
                {
                    foreach (ArraySegment<byte> buffer in data)
                    {
                        fs.Write(buffer.Array, buffer.Offset, buffer.Count);
                    }
                }
            }
            else
            {
                using (FileStream fs = File.Create(path))
                {
                    switch (format)
                    {
                        case GZipFormat:
                            GZipHeader gzipHeader = new GZipHeader();
                            GZipUtils.WriteGZipHeader(fs, gzipHeader);
                            break;
                        case ZLibFormat:
                            ZLibHeader zlibHeader = new ZLibHeader();
                            ZLibUtils.WriteZLibHeader(fs, zlibHeader);
                            break;
                    }

                    uint adler = ZLibUtils.Adler32Start;
                    uint crc = GZipUtils.Crc32Start;

                    using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Compress, true))
                    {
                        foreach (ArraySegment<byte> buffer in data)
                        {
                            ds.Write(buffer.Array, buffer.Offset, buffer.Count);
                            adler = ZLibUtils.Adler32(buffer.Array, buffer.Offset, buffer.Count, adler);
                            crc = GZipUtils.Crc(buffer.Array, buffer.Offset, buffer.Count, crc);
                        }
                    }

                    switch (format)
                    {
                        case GZipFormat:
                            uint isize = (uint)fs.Length;
                            GZipUtils.WriteCrcAndISize(fs, crc, isize);
                            break;
                        case ZLibFormat:
                            ZLibUtils.WriteAdler32(fs, adler);
                            break;
                    }
                }
            }
        }

        const int BufferSize = 0x4000;

        static IEnumerable<ArraySegment<byte>> ReadData(string format, string path)
        {
            if (format == BinaryFormat)
            {
                using (FileStream fs = File.OpenRead(path))
                {
                    byte[] buffer = new byte[BufferSize];
                    int read = fs.Read(buffer, 0, BufferSize);
                    while (read > 0)
                    {
                        yield return new ArraySegment<byte>(buffer, 0, read);
                        read = fs.Read(buffer, 0, BufferSize);
                    }
                }
            }
            else
            {
                using (FileStream fs = File.OpenRead(path))
                {
                    switch (format)
                    {
                        case GZipFormat:
                            GZipHeader gzipHeader = GZipUtils.ReadGZipInfo(fs);
                            break;
                        case ZLibFormat:
                            ZLibHeader zlibHeader = ZLibUtils.ReadZLibHeader(fs);
                            break;
                    }

                    uint adler = ZLibUtils.Adler32Start;
                    uint crc = GZipUtils.Crc32Start;
                    long uncompressedLength = 0;
                    using (DeflateStream ds = new DeflateStream(fs, CompressionMode.Decompress, true))
                    {
                        byte[] buffer = new byte[BufferSize];
                        int read = ds.Read(buffer, 0, BufferSize);
                        while (read > 0)
                        {
                            yield return new ArraySegment<byte>(buffer, 0, read);

                            adler = ZLibUtils.Adler32(buffer, 0, read, adler);
                            crc = GZipUtils.Crc(buffer, 0, read, crc);
                            uncompressedLength += read;

                            read = ds.Read(buffer, 0, BufferSize);
                        }
                    }

                    switch (format)
                    {
                        case GZipFormat:
                            uint fileCrc, isize;
                            GZipUtils.ReadCrcAndISize(fs, out fileCrc, out isize);
                            if (fileCrc != crc) throw new ApplicationException("Incorrect GZip CRC");
                            if ((uint)uncompressedLength != isize) throw new ApplicationException("Incorrect GZip iSize");
                            break;
                        case ZLibFormat:
                            uint fileAdler = ZLibUtils.ReadAdler32(fs);
                            if (fileAdler != adler) throw new ApplicationException("Incorrect ZLib Adler");
                            break;
                    }

                    if (fs.Position < fs.Length)
                    {
                        Console.Error.WriteLine("WARNING: more input data available");
                    }
                }
            }
        }

        static void PrintUsage()
        {
            Console.WriteLine("USAGE: CopyDeflate.exe <input-format> <input-file> <output-format> <output-file>");
            Console.WriteLine();
            Console.WriteLine("Formats:");
            Console.WriteLine("  -binary");
            Console.WriteLine("  -deflate");
            Console.WriteLine("  -gzip");
            Console.WriteLine("  -zlib");
            Console.WriteLine();
            Console.WriteLine("Examples:");
            Console.WriteLine("  CopyDeflate.exe -binary image.svg -gzip image.svgz");
            Console.WriteLine("  CopyDeflate.exe -deflate stream.bin -binary image.html");
            Console.WriteLine();
        }
    }
}
